/*
 * License:
 * 
 */
package co.springtree.spectrum.visa.api;

import org.bridj.Pointer;
import visa32.Visa32Library;
import visatype.VisatypeLibrary;

/**
 * Super class that provides wrapper functions around the VISA API.
 * @author gunter.fuchs
 * @todo Release pointers.
 */
public class Visa {
  // What is called viSession in Visa DLL becomes Handler in JVisa
  // so that viSession is not mistaken for a Java object.
  // @todo We want this be set to 0 only with the first instance.
  static private long visaResourceManagerHandle = 0;
  //private long visaResourceManagerHandle = 0;
  private long visaInstrumentHandle = 0;
  protected final int RESPONSE_BUFFER_DEFAULT_SIZE = 1024;
  public final long VISA_JAVA_ERROR = Visa32Library.VI_ERROR_SYSTEM_ERROR + 1;
  public boolean debugLog = false;
  

  /**
   * @return the visaResourceManagerHandle
   */
  public long getVisaResourceManagerHandle() {
    return visaResourceManagerHandle;
  }
  
  /**
   * Creates a session for a default resource manager.
   * @return status of the operation
   */
  public long openDefaultResourceManager() {
    long visaStatus = VISA_JAVA_ERROR;
    Pointer pViSession = Pointer.NULL;
    try {
      printDebug("\nOpen resource manager.");
      pViSession = Pointer.allocateLong();
      visaStatus = Visa32Library.viOpenDefaultRM(pViSession);
      printDebug(String.format("visaStatus = 0x%08X", visaStatus));
      visaResourceManagerHandle = (long) pViSession.get();
      if (visaResourceManagerHandle != 0) {
        printStatusDescription(visaStatus, "openDefaultResourceManager");
      }
    }
    catch (Exception e) {
      System.err.println(e.toString());
      visaStatus = VISA_JAVA_ERROR;
    }
    finally {
      if (pViSession != Pointer.NULL) {
        pViSession.release();
      }
      return visaStatus;
    }
  }

  /**
   * Closes resource manager.
   * @return status of the operation
   */
  public long closeResourceManager() {
    long visaStatus = VISA_JAVA_ERROR;
    try {
      printDebug("\nClose resource manager.");
      visaStatus = Visa32Library.viClose(visaResourceManagerHandle);
      printStatusDescription(visaStatus, "closeResourceManager");
      visaResourceManagerHandle = 0;
    }
    catch (Exception e) {
      System.err.println(e.toString());
      visaStatus = VISA_JAVA_ERROR;
    }
    finally {
      return visaStatus;
    }
  }
  
  /**
   * Gets the version of the resource.
   * @param viSession Visa handle
   * @return status of the operation
   */
  public long getResourceVersion(long viSession, VisaReturn version) {
    long visaStatus = VISA_JAVA_ERROR;
    long viVersion = 0;
    Pointer pVoid = Pointer.NULL;
    try {
      printDebug("\nGet resource version.");
      pVoid = Pointer.allocateLong();
      visaStatus = Visa32Library.viGetAttribute(
              viSession, 
              Visa32Library.VI_ATTR_RSRC_SPEC_VERSION, 
              pVoid);
      printStatusDescription(visaStatus, "viGetAttribute");
      viVersion = (long) pVoid.getLong();
      printDebug(String.format("Resource version = 0x%08X", viVersion));
    }
    catch (Exception e) {
      System.err.println(e.toString());
      visaStatus = VISA_JAVA_ERROR;
    }
    finally {
      version.setLong(viVersion);
      if (pVoid != Pointer.NULL) {
        pVoid.release();
      }
      return visaStatus;
    }
  }
  
  
  /**
   * Opens an instrument session.
   * @param instrument string that contains the instrument address and bus interface
   * @return status of the operation
   */
  public long openInstrument(String instrument) {
    long visaStatus = VISA_JAVA_ERROR;
    Pointer pViInstrument = Pointer.NULL;
    try {
      printDebug(String.format("\nOpen instrument %s.", instrument));
      pViInstrument = Pointer.allocateLong();
//      Pointer<Byte> pViString = Pointer.pointerToCString(instrument);
      Pointer<Byte> pViString = Pointer.allocateBytes(instrument.length() + 1);
      byte[] instrumentBytes = instrument.getBytes();
      pViString.setBytes(instrumentBytes);
      pViString.setByteAtIndex(instrument.length(), (byte) 0);
      visaStatus = Visa32Library.viOpen(
              visaResourceManagerHandle, 
              pViString, 
              (long) 0, // access mode (locking or not). 0:Use Visa default
              (long) 0, // timeout, only when access mode equals locking,
              pViInstrument // pointer to instrument object
              );
      printStatusDescription(visaStatus, "viOpen");
      if (visaStatus != VisatypeLibrary.VI_SUCCESS) {
        System.err.println(String.format("Error: Could not open session for %s.", instrument));
      }
      else {
        if (visaStatus == VisatypeLibrary.VI_SUCCESS) {
          visaInstrumentHandle = (long) pViInstrument.get();
          printDebug(String.format("viInstrument = 0x%08X.", visaInstrumentHandle));
        }
        else {
          visaInstrumentHandle = 0;
        }
      }
    }
    catch (Exception e) {
      System.err.println(e.toString());
      visaStatus = VISA_JAVA_ERROR;
    }
    finally {
      if (pViInstrument != Pointer.NULL) {
        pViInstrument.release();
      }
      return visaStatus;
    }
  }
  
  
  /**
   * Closes an instrument session.
   * @return status of the operation
   */
  public long closeInstrument() {
    long visaStatus = VISA_JAVA_ERROR;
    try {
      printDebug("\nClose instrument.");
      visaStatus = Visa32Library.viClose(visaInstrumentHandle);
      printStatusDescription(visaStatus, "viClose");
      if (visaStatus != VisatypeLibrary.VI_SUCCESS) {
        System.err.println("Error: Could not close session.");
        return visaStatus;
      }
    }
    catch (Exception e) {
      System.err.println(e.toString());
      visaStatus = VISA_JAVA_ERROR;
    }
    finally {
      return visaStatus;
    }
  }
  
  
  /**
   * @return the visaInstrumentHandle
   */
  public long getVisaInstrumentHandle() {
    return visaInstrumentHandle;
  }

  
  /**
   * Sends buffer content (usually a command) to the instrument.
   * @param command command or other ASCII data
   * @return status of the operation
   */
  public long write(String command) {
    long visaStatus = VISA_JAVA_ERROR;
    Pointer<Byte> pBuffer = Pointer.NULL;
    Pointer pReturnCount = Pointer.NULL;
    try {
      printDebug(String.format("\nWrite command \"%s\".", command));
      long commandLength = command.length();
      pBuffer = Pointer.pointerToCString(command);
      pBuffer.setString(command, Pointer.StringType.C);
      pReturnCount = Pointer.allocateLong();
      visaStatus = Visa32Library.viWrite(visaInstrumentHandle, pBuffer, commandLength, pReturnCount);
      printStatusDescription(visaStatus, "viWrite");
      if (visaStatus != VisatypeLibrary.VI_SUCCESS) {
        System.err.println(String.format("Error: Could not write %s.", command));
      }
      else {
        long returnCount = (long) pReturnCount.get();
        if (returnCount != commandLength) {
          System.err.println(String.format("Error: Could only write %d instead of %d bytes.", 
                                            returnCount, commandLength));
          visaStatus = VISA_JAVA_ERROR;
        }
      }
    }
    catch (Exception e) {
      System.err.println(e.toString());
      visaStatus = VISA_JAVA_ERROR;
    }
    finally {
      if (pBuffer != Pointer.NULL) {
        pBuffer.release();
      }
      if (pReturnCount != Pointer.NULL) {
        pReturnCount.release();
      }
      return visaStatus;
    }
  }
  
  
  /**
   * Reads something from the instrument, usually a command response.
   * @param response response object
   * @param bufferSize size of response buffer in bytes
   * @param isAscii response is ASCII, otherwise it is binary
   * @return status of the operation
   */
  public long read(VisaReturn response, int bufferSize, boolean isAscii) {
    long visaStatus = VISA_JAVA_ERROR;
    Pointer<Byte> pReadBuffer  = Pointer.NULL;
    Pointer pReturnCount = Pointer.NULL;
    try {
      printDebug("\nRead response.");
      pReadBuffer = Pointer.allocateBytes(bufferSize);
      pReturnCount = Pointer.allocateLong();
      visaStatus = Visa32Library.viRead(visaInstrumentHandle, pReadBuffer, bufferSize, pReturnCount);
      printStatusDescription(visaStatus, "viRead");
      if (visaStatus == VisatypeLibrary.VI_SUCCESS) {
        long returnCount = (long) pReturnCount.get();
        if (returnCount == 0) {
          System.err.println("Error: Did not receive a response.");
          visaStatus = VISA_JAVA_ERROR;
        }
        else {
          if (isAscii) {
            response.returnString = pReadBuffer.getString(Pointer.StringType.C);
            printDebug(response.returnString);
          }
          else {
            // Do we have to copy the bytes or is the line below okay?
            response.returnBytes = pReadBuffer.getBytes((int) returnCount);
          }
        }
      }
    }
    catch (Exception e) {
      System.err.println(e.toString());
      visaStatus = VISA_JAVA_ERROR;
    }
    finally {
      if (pReadBuffer != Pointer.NULL) {
        pReadBuffer.release();
      }
      if (pReturnCount != Pointer.NULL) {
        pReturnCount.release();
      }
      return visaStatus;
    }
  }
  
  
  /**
   * Reads something from the instrument, usually a command response.
   * @param response response object
   * @return status of the operation
   */
  public long read(VisaReturn response) {
    return read(response, RESPONSE_BUFFER_DEFAULT_SIZE, true);
  }
  
  
  public long clear() {
    long visaStatus = VISA_JAVA_ERROR;
    try {
      printDebug("\nSend viClear.");
      visaStatus = Visa32Library.viClear(visaInstrumentHandle);
      printStatusDescription(visaStatus, "viClear");
      if (visaStatus != VisatypeLibrary.VI_SUCCESS) {
        System.err.println("Error: Could not send viClear.");
      }
    }
    catch (Exception e) {
      System.err.println(e.toString());
      visaStatus = VISA_JAVA_ERROR;
    }
    finally {
      return visaStatus;
    }
  }
  
  
  /**
   * Converts a VISA status to a descriptive string.
   * @param visaStatus
   * @return status string
   */
  public String getStatusDescription(long visaStatus) {
    String statusString = String.format("VISA status 0x%08X", visaStatus);
    if (visaResourceManagerHandle == 0) {
      return String.format("%s: No description is available because no resource " +
              "manager session is open.", statusString);
    }
    Pointer pStatusDesc = Pointer.allocateBytes(1024);
    Visa32Library.viStatusDesc(visaResourceManagerHandle, visaStatus, pStatusDesc);
    return String.format("%s: %s", 
                statusString, pStatusDesc.getString(Pointer.StringType.C));
  }
  
  
  /**
   * Prints viStatus in hex, what function was executed, and the description of
   * viStatus.
   * @param viSession Visa handle
   * @param visaStatus return value from a Visa function
   * @param function name of the function
   */
  protected void printStatusDescription(long visaStatus, String function) {
    Pointer pStatusDesc = Pointer.NULL;
    try {
      if (visaStatus == VisatypeLibrary.VI_SUCCESS && debugLog == false) {
        return;
      }
      String status = String.format("viStatus(%s) = 0x%08X", function, visaStatus);
      if (visaStatus == VisatypeLibrary.VI_SUCCESS)
      {
        System.out.println(status);
      }
      else {
        System.err.println(status);
      }
      if (visaResourceManagerHandle == 0) {
        return;
      }
      pStatusDesc = Pointer.allocateBytes(RESPONSE_BUFFER_DEFAULT_SIZE);
      Visa32Library.viStatusDesc(visaResourceManagerHandle, visaStatus, pStatusDesc);
      String statusDescription = pStatusDesc.getString(Pointer.StringType.C);
      if (visaStatus == VisatypeLibrary.VI_SUCCESS) {
        System.out.println(statusDescription);
      }
      else {
        System.err.println(statusDescription);
      }
    }
    catch (Exception e) {
      System.err.println(e.toString());
    }
    finally {
      if (pStatusDesc != Pointer.NULL) {
        pStatusDesc.release();
      }
    }
  }
  
  
  /**
   * Prints a debug string if debug logging is turned on.
   * @param debug string to print
   */
  private void printDebug(String debug) {
    if (debugLog == true) {
      System.out.println(debug);
    }
  }
}
